/*
 * Copyright 2007 - 2009 JEuclid, http://jeuclid.sf.net
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: JEuclidElementFactory.java,v 92569cce5dd6 2009/07/27 14:50:38 maxberger $ */

package mathml.elements;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;


import mathml.elements.content.semantic.Annotation;
import mathml.elements.content.semantic.Semantics;
import mathml.elements.generic.ForeignElement;
import mathml.elements.generic.MathImpl;
import mathml.elements.presentation.enlivening.Maction;
import mathml.elements.presentation.general.Menclose;
import mathml.elements.presentation.general.Merror;
import mathml.elements.presentation.general.Mfenced;
import mathml.elements.presentation.general.Mfrac;
import mathml.elements.presentation.general.Mpadded;
import mathml.elements.presentation.general.Mphantom;
import mathml.elements.presentation.general.Mroot;
import mathml.elements.presentation.general.Mrow;
import mathml.elements.presentation.general.Msqrt;
import mathml.elements.presentation.general.Mstyle;
import mathml.elements.presentation.script.Mmultiscripts;
import mathml.elements.presentation.script.Mover;
import mathml.elements.presentation.script.Mprescripts;
import mathml.elements.presentation.script.Msub;
import mathml.elements.presentation.script.Msubsup;
import mathml.elements.presentation.script.Msup;
import mathml.elements.presentation.script.Munder;
import mathml.elements.presentation.script.Munderover;
import mathml.elements.presentation.script.None;
import mathml.elements.presentation.table.Maligngroup;
import mathml.elements.presentation.table.Malignmark;
import mathml.elements.presentation.table.Mlabeledtr;
import mathml.elements.presentation.table.Mtable;
import mathml.elements.presentation.table.Mtd;
import mathml.elements.presentation.table.Mtr;
import mathml.elements.presentation.token.Mglyph;
import mathml.elements.presentation.token.Mi;
import mathml.elements.presentation.token.Mn;
import mathml.elements.presentation.token.Mo;
import mathml.elements.presentation.token.Ms;
import mathml.elements.presentation.token.Mspace;
import mathml.elements.presentation.token.Mtext;

import org.apache.batik.dom.AbstractDocument;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;


/**
 * Creates MathElements from given element strings.
 * 
 * @version $Revision: 92569cce5dd6 $
 */
public final class MathMLElementFactory {

    /**
     * Logger for this class
     */
    private static final Log LOGGER = LogFactory
            .getLog(MathMLElementFactory.class);

    private static final Map<String, Constructor<?>> IMPL_CLASSES = new HashMap<String, Constructor<?>>();;

    private MathMLElementFactory() {
        // Empty on purpose
    }

    private static String removeNSPrefix(final String qualifiedName) {
        final int posSeparator = qualifiedName.indexOf(':');
        if (posSeparator >= 0) {
            return qualifiedName.substring(posSeparator + 1);
        }
        return qualifiedName;
    }

    /**
     * Factory for MathML Elements.
     * 
     * @param nsUri
     *            namespace URI. May be null. May be ignored in the case of
     *            MathML.
     * @param qualifiedName
     *            name of the element with optional namespace prefix.
     * @param ownerDocument
     *            Document this element belongs to.
     * @return A new MathElement for this tag name.
     */
    public static Element elementFromName(final String nsUri,
            final String qualifiedName, final Document ownerDocument) {

        final String localName = MathMLElementFactory
                .removeNSPrefix(qualifiedName);

        MElement element = null;

        if ((nsUri == null) || (AbstractMathMLElement.URI.equals(nsUri))) {
            final Constructor<?> con = MathMLElementFactory.IMPL_CLASSES
                    .get(localName);

            if (con != null) {
                try {
                    element = (MElement) con.newInstance(qualifiedName,
                            ownerDocument);
                } catch (final InstantiationException e) {
                    element = null;
                } catch (final IllegalAccessException e) {
                    element = null;
                } catch (final InvocationTargetException e) {
                    element = null;
                }
            }
        }
        if (element == null) {
            // JEuclidElementFactory.LOGGER.info("Unsupported element: "
            // + localName);
            element = new ForeignElement(nsUri, qualifiedName,
                    (AbstractDocument) ownerDocument);
        }
        return element;
    }

    private static void addClass(final Class<?> c) {
        try {
            final Field f = c.getField("ELEMENT");
            final String tag = (String) f.get(null);
            MathMLElementFactory.IMPL_CLASSES.put(tag, c.getConstructor(
                    String.class, AbstractDocument.class));
        } catch (final NoSuchFieldException e) {
            MathMLElementFactory.LOGGER.warn(c.toString(), e);
        } catch (final NoSuchMethodException e) {
            MathMLElementFactory.LOGGER.warn(c.toString(), e);
        } catch (final IllegalAccessException e) {
            MathMLElementFactory.LOGGER.warn(c.toString(), e);
        }

    }

    // CHECKSTYLE:OFF
    static {
        // CHECKSTYLE:ON
        MathMLElementFactory.addClass(MathImpl.class);
        MathMLElementFactory.addClass(Mfenced.class);
        MathMLElementFactory.addClass(Mfrac.class);
        MathMLElementFactory.addClass(Menclose.class);
        MathMLElementFactory.addClass(Mphantom.class);
        MathMLElementFactory.addClass(Msup.class);
        MathMLElementFactory.addClass(Msub.class);
        MathMLElementFactory.addClass(Mmultiscripts.class);
        MathMLElementFactory.addClass(Mprescripts.class);
        MathMLElementFactory.addClass(None.class);
        MathMLElementFactory.addClass(Msubsup.class);
        MathMLElementFactory.addClass(Munder.class);
        MathMLElementFactory.addClass(Mover.class);
        MathMLElementFactory.addClass(Munderover.class);
        MathMLElementFactory.addClass(Mspace.class);
        MathMLElementFactory.addClass(Ms.class);
        MathMLElementFactory.addClass(Mstyle.class);
        MathMLElementFactory.addClass(Msqrt.class);
        MathMLElementFactory.addClass(Mroot.class);
        MathMLElementFactory.addClass(Mtable.class);
        MathMLElementFactory.addClass(Mtr.class);
        MathMLElementFactory.addClass(Mlabeledtr.class);
        MathMLElementFactory.addClass(Mtd.class);
        MathMLElementFactory.addClass(Mo.class);
        MathMLElementFactory.addClass(Mi.class);
        MathMLElementFactory.addClass(Mn.class);
        MathMLElementFactory.addClass(Mtext.class);
        MathMLElementFactory.addClass(Mrow.class);
        MathMLElementFactory.addClass(Maligngroup.class);
        MathMLElementFactory.addClass(Malignmark.class);
        MathMLElementFactory.addClass(Semantics.class);
        MathMLElementFactory.addClass(Annotation.class);
        MathMLElementFactory.addClass(Mpadded.class);
        MathMLElementFactory.addClass(Merror.class);
        MathMLElementFactory.addClass(Maction.class);
        MathMLElementFactory.addClass(Mglyph.class);
    }

}
